import cv2
import os
import shutil
import math
import numpy as np
from enum import Enum
from skimage.metrics import structural_similarity
from collections import Counter
from sympy import symbols, Eq, solve
import matplotlib.pyplot as plt
from scipy.interpolate import make_interp_spline
from openpyxl import Workbook
from collections import defaultdict
import pwlf
from sklearn.metrics import mean_squared_error
from ultralytics import YOLO

SIM = 0.9
YOLO_CONF = 0.88
model = YOLO('best.pt')


class Direction(Enum):
    Left = 1  # 左滑
    Right = 2  # 右滑


class Frame:
    def __init__(self, index, orig_image, gray_image):
        self.index = index
        self.orig_image = orig_image
        self.gray_image = gray_image
        self.line_list_merged = list()  # 合并后的lines 格式：只有x坐标
        self.line_list_orig = list()  # 原图中的lines 格式：只有x坐标
        self.line_list_changed = list()  # 变化图中的lines 格式：只有x坐标
        self.line_list_classified = list()  # 元素为2元组(index , x), index是分组，x是线的x坐标
        self.line_list_bool = list()  # 用于标记当前line是否被使用过
        self.yolo_result = list()  # dict : { id, cls, box, conf } 分别为对应的id，类别，位置，置信度

    def add_line_list_orig(self, lst):
        self.line_list_orig = list(set(self.line_list_orig + lst))

    def add_line_list_changed(self, lst):
        self.line_list_changed = list(set(self.line_list_changed + lst))

    def merge_lines(self):
        # print(f'merged lines : {self.index}')
        # print(self.line_list_orig)
        # print(self.line_list_changed)
        for x_dilated in self.line_list_changed:
            for x_orig in self.line_list_orig:
                if abs(x_dilated - x_orig) <= 10:
                    self.line_list_merged.append(x_dilated)
                    break
        # print('merged lines of orig and dilated')
        # print(self.line_list_merged)
        # 去除边缘的线
        width = self.orig_image.shape[1]
        for line in self.line_list_merged:
            if line < 20 or abs(width - line) < 20:
                self.line_list_merged.remove(line)
        self.line_list_bool = [0 for _ in range(len(self.line_list_merged))]

    def draw_merged_lines(self):
        tmp_pic = self.orig_image.copy()
        for x in self.line_list_merged:
            cv2.line(tmp_pic, (x, 0), (x, 2000), (0, 0, 255), 2)
        cv2.imwrite(video_name + f"/merged_{self.index}.jpg", tmp_pic)

    def reset_line_list_bool(self):
        for i in range(len(self.line_list_bool)):
            self.line_list_bool[i] = 0

    def add_yolo_result(self, yolo_result):
        self.yolo_result = yolo_result


def yolo_frame(frame):
    # 获取每一帧的yolo目标追踪结果
    results = model.track(frame, persist=True)

    # Visualize the results on the frame
    annotated_frame = results[0].plot()

    # Get the boxes and track IDs
    type_dict = results[0].names
    boxes = results[0].boxes
    track_ids = results[0].boxes.id.tolist()

    yolo_result = []
    for box, track_id in zip(boxes, track_ids):
        t = type_dict[int(box.cls[0])]
        conf = float(box.conf)
        if conf > YOLO_CONF:
            x0, y0, x1, y1 = [int(i) for i in box.xyxy[0].tolist()]

            od_item = {
                'id': int(track_id),
                'cls': t,
                'box': [x0, y0, x1, y1],
                'conf': round(conf, 2)
            }
            yolo_result.append(od_item)
    print(yolo_result)
    return yolo_result, annotated_frame


def same_lines(direction, frame1, line_index_1, frame2, line_index_2):
    height, width, _ = frame1.orig_image.shape
    default_width = 100  # 默认比较的宽度
    line_x_1 = frame1.line_list_merged[line_index_1]
    line_x_2 = frame2.line_list_merged[line_index_2]
    if direction == Direction.Left:  # 向左划则检查右边的像素

        if abs(width - max((line_x_1, line_x_2))) < default_width:
            default_width = abs(width - max((line_x_1, line_x_2)))
        if default_width < 6:
            return 0
        pixels_1 = frame1.orig_image[:, line_x_1:line_x_1 + default_width, :]  # 图一默认宽度内的像素
        pixels_2 = frame2.orig_image[:, line_x_2:line_x_2 + default_width, :]  # 图二默认宽度内的像素
        # print(default_width)
        similarity, _ = structural_similarity(pixels_1, pixels_2, win_size=3, full=True)  # 计算ssim相似度
    else:  # 向右划则检查左边像素
        if min(line_x_1, line_x_2) < default_width:
            default_width = min(line_x_1, line_x_2)
        if default_width < 6:
            return 0
        pixels_1 = frame1.orig_image[:, line_x_1 - default_width:line_x_1, :]  # 图一默认宽度内的像素
        pixels_2 = frame2.orig_image[:, line_x_2 - default_width:line_x_2, :]  # 图二默认宽度内的像素
        # print(default_width)
        similarity, _ = structural_similarity(pixels_1, pixels_2, win_size=3, full=True)  # 计算ssim相似度
    return similarity


# 得到全部帧的相同线段
def get_frames_same_line(frame_list, direction):
    # 先对每个帧初始化 line_list_merged
    for frame in frame_list:
        frame.merge_lines()
    feather_index = 0  # 用来对线进行分类计数
    classified_lines_list = dict()  # 内容是三元组 （frame_index, line_index, line的帧)
    # 遍历第1个帧
    for frame_index_1 in range(len(frame_list)):
        for line_index_1 in range(len(frame_list[frame_index_1].line_list_merged)):

            if frame_list[frame_index_1].line_list_bool[line_index_1] == 0:  # 第1个帧中没有归类的line
                classified_lines_list[str(feather_index)] = [(frame_index_1, line_index_1, frame_list[frame_index_1].line_list_merged[line_index_1])]  # 创建新分组

                frame_list[frame_index_1].line_list_bool[line_index_1] = 1  # 标注
                cur_line = (frame_index_1, line_index_1)
                # 遍历第2个帧
                for frame_index_2 in range(frame_index_1 + 1, len(frame_list)):
                    similarity_dict = dict()  # 用来找图中最匹配的那一条线
                    for line_index_2 in range(len(frame_list[frame_index_2].line_list_merged)):
                        if frame_list[frame_index_2].line_list_bool[line_index_2] == 0:  # 第2个帧中没有归类的line
                            similarity = same_lines(direction, frame_list[cur_line[0]], cur_line[1], frame_list[frame_index_2], line_index_2)
                            print(f'line_1_x : {frame_list[frame_index_1].line_list_merged[line_index_1]} ** line_2_x : {frame_list[frame_index_2].line_list_merged[line_index_2]} ** sim : {similarity}')
                            if similarity > SIM:
                                # 如果相似，则保存在 similarity_dict 中
                                similarity_dict[line_index_2] = similarity
                    # 取出 similarity_dict 中最相似的一条线
                    if similarity_dict:
                        print(f'frame [{frame_index_2}] similarity_dict {similarity_dict}')
                        max_line_index_2 = max(similarity_dict, key=lambda x: similarity_dict[x])
                        print(frame_list[frame_index_2].line_list_merged[max_line_index_2])
                        classified_lines_list[str(feather_index)].append((frame_index_2, max_line_index_2, frame_list[frame_index_2].line_list_merged[max_line_index_2]))  # 加入分组
                        frame_list[frame_index_2].line_list_bool[max_line_index_2] = 1  # 标注
                        cur_line = (frame_index_2, max_line_index_2)
                feather_index += 1
    # 过滤掉线条数小于3个的线段标记（小于3个不能画轨迹图）
    keys_to_remove = []
    for key, value in classified_lines_list.items():
        if len(value) <= 3:
            keys_to_remove.append(key)
    for key in keys_to_remove:
        del classified_lines_list[key]

    print(classified_lines_list)
    # del_illegal_lines(classified_lines_list, direction)
    update_line_list_classified(frame_list, classified_lines_list)
    return classified_lines_list


def get_adjacent_frames_same_line(frame1: Frame, frame2: Frame, direction: Direction):
    frame1.reset_line_list_bool()
    frame2.reset_line_list_bool()
    classified_lines_list = list()
    feather_index = 0  # 用来对线进行分类计数
    similarity_list = list()  # 内容是三元组 （line_index_1, line_index_2, 相似度)
    # 遍历第1个帧
    # for line_index_1 in range(len(frame1.line_list_merged)):
    #
    #     if frame1.line_list_bool[line_index_1] == 0:  # 第1个帧中没有归类的line
    #         classified_lines_list[str(feather_index)] = [(1, line_index_1, frame1.line_list_merged[line_index_1])]  # 创建新分组
    #
    #         frame1.line_list_bool[line_index_1] = 1  # 标注
    #         # 遍历第2个帧
    #         similarity_dict = dict()  # 用来找图中最匹配的那一条线
    #         for line_index_2 in range(len(frame2.line_list_merged)):
    #             if frame2.line_list_bool[line_index_2] == 0:  # 第2个帧中没有归类的line
    #                 similarity = same_lines(direction, frame1, line_index_1, frame2, line_index_2)
    #                 # print(f'line_1_x : {frame1.line_list_merged[line_index_1]} ** line_2_x : {frame2.line_list_merged[line_index_2]} ** sim : {similarity}')
    #                 if similarity > SIM:
    #                     # 如果相似，则保存在 similarity_dict 中
    #                     similarity_dict[line_index_2] = similarity
    #         # 取出 similarity_dict 中最相似的一条线
    #         if similarity_dict:
    #             print(f'frame [{2}] similarity_dict {similarity_dict}')
    #             max_line_index_2 = max(similarity_dict, key=lambda x: similarity_dict[x])
    #             # print(frame2.line_list_merged[max_line_index_2])
    #             classified_lines_list[str(feather_index)].append((2, max_line_index_2, frame2.line_list_merged[max_line_index_2]))  # 加入分组
    #             frame2.line_list_bool[max_line_index_2] = 1  # 标注
    #         feather_index += 1
    # 找到第一帧的位移
    if frame1.index < 0:
        if direction == Direction.Left:
            for line_index_2 in range(len(frame2.line_list_merged)):
                classified_lines_list.append(abs(frame2.orig_image.shape[1] - frame2.line_list_merged[line_index_2]))
        elif direction == Direction.Right:
            for line_index_2 in range(len(frame2.line_list_merged)):
                classified_lines_list.append(frame2.line_list_merged[line_index_2])
    # 找到最后一帧的位移
    elif frame2.index < 0:
        if direction == Direction.Left:
            for line_index_1 in range(len(frame1.line_list_merged)):
                classified_lines_list.append(frame1.line_list_merged[line_index_1])
        elif direction == Direction.Right:
            for line_index_2 in range(len(frame1.line_list_merged)):
                classified_lines_list.append(abs(frame1.orig_image.shape[1] - frame1.line_list_merged[line_index_2]))
    # 计算所有的相似度
    else:
        for line_index_1 in range(len(frame1.line_list_merged)):
            for line_index_2 in range(len(frame2.line_list_merged)):
                similarity = same_lines(direction, frame1, line_index_1, frame2, line_index_2)
                similarity_list.append((line_index_1, line_index_2, similarity))
        # 找到相似度中最大的
        sorted_similarity_list = sorted(similarity_list, key=lambda x: x[2], reverse=True)
        print(sorted_similarity_list)
        for similarity_triple in sorted_similarity_list:
            line_index_1, line_index_2, similarity = similarity_triple
            if similarity > SIM and frame1.line_list_bool[line_index_1] == 0 and frame2.line_list_bool[line_index_2] == 0:
                frame1.line_list_bool[line_index_1] = 1
                frame2.line_list_bool[line_index_2] = 1
                classified_lines_list.append(abs(frame1.line_list_merged[line_index_1] - frame2.line_list_merged[line_index_2]))
                if True:
                    tmp_pic_1 = draw_lines(frame1.orig_image, [(frame1.line_list_merged[line_index_1], 0, frame1.line_list_merged[line_index_1], 2000)])
                    tmp_pic_2 = draw_lines(frame2.orig_image, [(frame2.line_list_merged[line_index_2], 0, frame2.line_list_merged[line_index_2], 2000)])
                    cv2.imwrite(video_name + f"/lines_{frame1.index}_{feather_index}_1.jpg", tmp_pic_1)
                    cv2.imwrite(video_name + f"/lines_{frame2.index}_{feather_index}_2.jpg", tmp_pic_2)
                    feather_index += 1
                    # break  # 只取相似度最大的一个
    print(f'\nframe 【{frame1.index}】 & frame 【{frame2.index}】 SIM : {classified_lines_list}')
    print(f'dissame line list : {classified_lines_list}')
    return classified_lines_list


def equal(a, b):
    if abs(a - b) <= 10:
        return True
    return False


def widget_is_move(widget1, widget2):
    # 判断控件是否移动
    # @ipnput ： widget --> dict : { id, cls, box, conf } 分别为对应的id，类别，位置，置信度
    if equal(widget1['box'][1], widget2['box'][1]) and equal(widget1['box'][3], widget2['box'][3]):  # y 坐标应该一致
        if not equal(widget1['box'][0], widget2['box'][0]) and not equal(widget1['box'][2], widget2['box'][2]):  # x 坐标应该不一致
            return True
    return False


def widget_move_distance(widget1, widget2, direction):
    if direction == Direction.Left:  # 左滑则左边缘先出现，比较 x0
        return round(abs(widget1['box'][0]- widget2['box'][0]))
    if direction == Direction.Right:  # 右滑则右边缘先出现，比较 x1
        return round(abs(widget1['box'][2]- widget2['box'][2]))
    return 0


def get_yolo_distance(frame1: Frame, frame2: Frame, direction: Direction):
    # 通过找到yolo相同的控件找到两帧之间的位移
    dis_yolo_list = list()
    for widget1 in frame1.yolo_result:
        for widget2 in frame2.yolo_result:
            if widget1['id'] == widget2['id'] and widget_is_move(widget1, widget2):
                dis_yolo_list.append(widget_move_distance(widget1, widget2, direction))
    print(f'dis yolo list : {dis_yolo_list}')
    return dis_yolo_list


def get_frames_same_line_2(frame_list, direction):
    # 得到相邻帧的相同线段,采用的是相邻帧的方法
    # 先对每个帧初始化 line_list_merged
    x_t_list_without_repeat = [0 for _ in range(len(frame_list))]
    # frame_list_not_empty = [Frame(-1, None, None)]
    frame_list_not_empty = list()
    for frame in frame_list:
        frame.merge_lines()
        frame.draw_merged_lines()
    # 找到非空的 frame
    for frame in frame_list:
        if len(frame.line_list_merged) != 0:
            frame_list_not_empty.append(frame)
    # frame_list_not_empty.append(Frame(-1, None, None))
    for i in range(1, len(frame_list_not_empty)):
        dis_line_list = get_adjacent_frames_same_line(frame_list_not_empty[i - 1], frame_list_not_empty[i], direction)
        # TODO: yolo追踪应该存在于每两帧之间，同时也要存在于变化前的一帧和停止变化的后一帧（覆盖前后）
        dis_yolo_list = get_yolo_distance(frame_list_not_empty[i - 1], frame_list_not_empty[i], direction)
        # TODO: 要在这里加上易新的结果，需要给函数添加一个参数
        x_t_list_without_repeat[frame_list_not_empty[i].index] = filter_illegal_element(dis_line_list + dis_yolo_list)
    print(f'x_t list : {x_t_list_without_repeat}')
    return x_t_list_without_repeat


def del_illegal_lines(classified_lines_list, direction):
    # 考虑怎么找个一个伪上升队列/下降队列
    if direction == Direction.Left:
        for index, lines_list in classified_lines_list.items():
            lines_length = len(lines_list)
            del_list = list()
            for i in range(1, lines_length):
                if lines_list[i][2] == lines_list[i - 1][2]:
                    del_list.append(i - 1)
                if lines_list[i][2] < lines_list[i - 1][2]:
                    pass


def update_line_list_classified(frame_list, classified_lines_list):
    # 将分类后的结果更新到line_list_classified里
    for index, lines_list in classified_lines_list.items():
        for frame_index, line_index, line_x in lines_list:
            frame_list[frame_index].line_list_classified.append((index, line_x))


def print_same_line(video_name, frame_list, classified_lines_list):
    for index, lines_list in classified_lines_list.items():
        for frame_index, line_index, line_x in lines_list:
            draw_line(frame_list[frame_index].orig_image, line_x, index)
    for frame in frame_list:
        lines_name = video_name + f"/lines_{frame.index}.jpg"
        cv2.imwrite(lines_name, frame.orig_image)


def find_most_frequent_elements(lst):
    if len(lst) == 0:
        return []
    counter = Counter(lst)
    max_count = max(counter.values())
    most_frequent_elements = [element for element, count in counter.items() if count == max_count]
    return most_frequent_elements


def get_x_t_graph_2(frame_list):
    # 绘制xt图，单位是 像素/帧
    x_t_list = [(0, 0)]  # 列表中的元素是一个二元组 (x, t) 其中x是两帧之间的位移，t是帧的标号，初始是第1帧的位移为0
    l = len(frame_list)
    for i in range(1, l):
        for classified_lines_2 in frame_list[i].line_list_classified:
            distance_list = []
            for classified_lines_1 in frame_list[i - 1].line_list_classified:
                if classified_lines_1[0] == classified_lines_2[0]:
                    distance_list.append(classified_lines_2[1] - classified_lines_1[1])
            best_distance = find_most_frequent_elements(distance_list)
            for distance in best_distance:
                x_t_list.append((distance, frame_list[i].index))
    print(x_t_list)


def get_x_t_graph_3(classified_lines_list, direction, width):
    # 绘制xt图，单位是 像素/帧
    # @input ：classified_lines_list：dict()；  key()是相同分类线条的标号； value()元素内容是三元组 （frame_index, line_index, line的帧)
    # 去除0帧的情况，因为0帧不可能有变化
    for index, lines_list in classified_lines_list.items():
        if lines_list[0][0] == 0:
            del lines_list[0]
    print(classified_lines_list)
    # TODO: 给帧最小的一条线前面加上一个，能够处理凭空出现的一条线的情况（目前先不加，因为不知道哪条线才是移动导致产生的线）
    # min_line = 999
    # min_index = list()
    # for index, lines_list in classified_lines_list.items():
    #     index_0 = lines_list[0][0]
    #     print(index_0)
    #     if index_0 < min_line:
    #         min_index.clear()
    #         min_index.append(index)
    #         min_line = index_0
    #     elif index_0 == min_line:
    #         min_index.append(index)
    # if min_line >= 1:
    #     for index in min_index:
    #         if direction == Direction.Left:
    #             classified_lines_list[index].insert(0, (min_line - 1, 0, width))
    #         else:
    #             classified_lines_list[index].insert(0, (min_line - 1, 0, 0))
    # print(f'classified_lines_list : {classified_lines_list}')
    x_t_list = list()  # 列表中的元素是一个二元组 (t0, t1, x) 其中x是两帧之间的位移，t0是起始帧的标号，t1是位移中止帧的标号
    # 先算两个帧之间位移的距离
    for index, lines_list in classified_lines_list.items():
        lines_length = len(lines_list)
        for i in range(1, lines_length):
            if abs(lines_list[i][2] - lines_list[i - 1][2]) >= 3:  # 剔除掉位移过小的
                if direction == Direction.Left:  # 左滑的话应该逐渐减小
                    if lines_list[i][2] <= lines_list[i - 1][2]:
                        x_t_list.append((lines_list[i - 1][0], lines_list[i][0], abs(lines_list[i][2] - lines_list[i - 1][2])))
                if direction == Direction.Right:  # 右滑的话应该逐渐增大
                    if lines_list[i][2] >= lines_list[i - 1][2]:
                        x_t_list.append((lines_list[i - 1][0], lines_list[i][0], abs(lines_list[i][2] - lines_list[i - 1][2])))

    max_index = 0  # 最大的下标
    for x_t in x_t_list:
        max_index = max(max_index, x_t[1])
        if x_t[1] - x_t[0] != 1 or x_t[2] == 0:
            x_t_list.remove(x_t)
    print(f'x_t_list : {x_t_list}')
    x_t_list_with_repeat = [[] for _ in range(max_index + 1)]  # 有重复的 x_t 列表，例如 18到19帧有两个数据 303， 304，则 x_t_list_with_repeat[19] 为 [303, 304]
    x_t_list_without_repeat = list()  # 过滤掉重复的x_t列表
    for x_t in x_t_list:
        x_t_list_with_repeat[x_t[1]].append(x_t[2])
    for index, x_t in enumerate(x_t_list_with_repeat):
        x_t_list_without_repeat.append(filter_illegal_element(x_t))
        print(f'{index}  :  {x_t_list_without_repeat[index]}')
    print(f'x_t_list_without_repeat : {x_t_list_without_repeat}')
    # x_t_smooth = smooth_x_t(x_t_list_without_repeat)
    # print(f'x_t_smooth : {x_t_smooth}')
    # return x_t_smooth
    return x_t_list_without_repeat


def filter_illegal_element(lst):
    # 剔除异常数据，给定一个list，返回去除异常数据的平均值
    if not lst:
        return 0
    binary_list = []
    for ele in lst:
        if not binary_list:
            binary_list.append([ele])
        else:
            flag = False
            for l in binary_list:
                tmp_list = l + [ele]
                if max(tmp_list) - min(tmp_list) <= 10:
                    flag = True
                    l.append(ele)
                    break
            if flag is False:
                binary_list.append([ele])
    # 找出长度最长的子数组
    lenth_list = list()
    for l in binary_list:
        lenth_list.append(len(l))

    index_list = find_max_indices(lenth_list)  # 数值最大的元素的下标集合
    print(binary_list)
    # 如果只有一个最大集合，则返回平均值，否则说明误差较大，返回 0
    if len(index_list) == 1:
        return round(np.mean(binary_list[index_list[0]]))
    return 0


def find_max_indices(lst):
    # 找到数值最大的元素的下标集合
    max_value = -1
    max_indices = []

    for i, num in enumerate(lst):
        if num > max_value:
            max_value = num
            max_indices = [i]
        elif num == max_value:
            max_indices.append(i)
    return max_indices


def create_or_clear_folder(folder_path):
    # 检查文件夹是否存在
    if not os.path.exists(folder_path):
        # 如果文件夹不存在，则创建文件夹
        os.makedirs(folder_path)
    else:
        # 如果文件夹存在，则清空文件夹内的内容
        for filename in os.listdir(folder_path):
            file_path = os.path.join(folder_path, filename)
            if os.path.isfile(file_path):
                os.remove(file_path)
            elif os.path.isdir(file_path):
                shutil.rmtree(file_path)


def get_frame_line(frame, flag):
    # 获取每帧的贯穿的线

    # 获取变化图的lines
    # gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    edges = cv2.Canny(frame, 10, 20)  # 边缘检测轮廓图
    lines = cv2.HoughLinesP(edges, 1, math.pi / 180, threshold=15, minLineLength=25, maxLineGap=5)

    merged_lines = []
    THRESHOLD_X = 5
    if lines is not None:
        lines = lines.squeeze()  # 去除数组中的单维度
        lines = lines.tolist()
        # 防止产生一维数组
        if np.array(lines).ndim == 1:
            lines = [lines]
        lines = sorted(lines, key=lambda x: x[0])  # 按照x坐标排序

        if flag:  # 原图需要删除短的线
            for line in lines[:]:  # 确保当前是直线
                if line[0] != line[2] or abs(line[1] - line[3]) < 100:
                    lines.remove(line)
        else:  # 过滤图不需要删除
            for line in lines[:]:  # 确保当前是直线
                if line[0] != line[2]:
                    lines.remove(line)

        for line in lines:
            x1, y1, x2, y2 = line
            merged = False

            for merged_line in merged_lines:
                merged_x1, merged_y1, merged_x2, merged_y2 = merged_line
                if abs(x1 - merged_x1) < THRESHOLD_X:
                    # 合并线段
                    merged_line[0] = merged_line[2] = x1
                    merged_line[1] = max(y1, merged_y1)
                    merged_line[3] = min(y2, merged_y2)
                    merged = True
                    break

            if not merged:
                merged_lines.append([x1, y1, x2, y2])
        # print('merged lines')
        # print(merged_lines)
    # for line in merged_lines:
    #     x1, y1, x2, y2 = line
    #     cv2.line(orig_frame, (x1, y1), (x2, y2), (0, 0, 255), 2)
    # return edges, frame, orig_frame, orig_frame_with_lines
    return merged_lines


def draw_lines(frame, lines):
    # lines 是个 list， 其中的元素是一个四元组 [终点x，终点y，起点x，起点y]
    frame_copy = frame.copy()
    for line in lines:
        x1, y1, x2, y2 = line
        cv2.line(frame_copy, (x1, y1), (x2, y2), (0, 0, 255), 2)
    return frame_copy


def draw_line(frame, line, line_index):
    # line 是个 数字代表线条的x坐标
    cv2.line(frame, (line, 0), (line, 2000), (0, 0, 255), 2)
    cv2.putText(frame, line_index, (line, 100), cv2.FONT_HERSHEY_SIMPLEX, 3, (0, 255, 0), 2)


def lines2lines_x(lines):
    # input lines ：[终点x，终点y，起点x，起点y]
    # output lines ：只有x坐标
    lines_x = list()
    for line in lines:
        lines_x.append(line[0])
    return lines_x


def get_video_change(video_name):
    video = cv2.VideoCapture(video_name + '.mp4')
    # 读取第一帧
    ret, prev_frame = video.read()
    prev_gray = cv2.cvtColor(prev_frame, cv2.COLOR_BGR2GRAY)

    frame_count = 0
    save_count = 0
    create_or_clear_folder(video_name)
    # 计算图像的总像素数
    total_pixels = prev_frame.shape[0] * prev_frame.shape[1]

    frame_list = []

    while video.isOpened():
        # 读取当前帧
        ret, curr_frame = video.read()
        if not ret:
            break

        if frame_count % 1 == 0:
            # 将当前帧转换为灰度图像
            curr_gray = cv2.cvtColor(curr_frame, cv2.COLOR_BGR2GRAY)

            # 计算当前帧与前一帧的差异
            frame_diff = cv2.absdiff(curr_gray, prev_gray)

            # 应用阈值处理，将差异图像转换为二值图像（增加变化的显示）
            _, threshold = cv2.threshold(frame_diff, 10, 255, cv2.THRESH_BINARY)
            # 对二值图像先腐蚀再膨胀，去除噪声（先锐化再钝化）
            # eroded = cv2.erode(threshold, None, iterations=1)
            dilated = cv2.dilate(threshold, kernel=np.ones((3, 3), np.uint8), iterations=1)

            # 对阈值图进行直线检测
            # edges, frame, orig_frame, orig_frame_with_lines = get_frame_line(curr_frame, dilated, True)
            lines_orig = get_frame_line(prev_gray, True)  # 获取 prev_gray 的 lines
            lines_dilated = get_frame_line(dilated, False)  # 获取 dilated 的 lines
            # merged_lines = merge_lines(merged_lines_orig, merged_lines_dilated)
            # frame_with_lines = draw_lines(prev_frame, merged_lines)
            orig_frame_with_lines = draw_lines(prev_frame, lines_orig)
            orig_frame_with_lines_dilated = draw_lines(prev_frame, lines_dilated)

            frame_tmp = Frame(save_count, prev_frame, prev_gray)
            frame_tmp.add_line_list_orig(lines2lines_x(lines_orig))
            frame_tmp.add_line_list_changed(lines2lines_x(lines_dilated))
            # if len(frame_list) != 0:
            #     frame_list[-1].add_line_list_changed(lines2lines_x(lines_dilated))
            frame_list.append(frame_tmp)

            # 输出图像
            image_output(video_name, save_count, curr_frame, orig_frame_with_lines_dilated, dilated, orig_frame_with_lines)

            # 更新前一帧
            prev_frame = curr_frame
            prev_gray = curr_gray
            save_count += 1
        frame_count += 1

    # 倒叙添加yolo结果
    for frame in reversed(frame_list):
        yolo_result, yolo_image = yolo_frame(frame.orig_image)
        frame.add_yolo_result(yolo_result)
        img_name = video_name + f"/yolo_{frame.index}.jpg"
        cv2.imwrite(img_name, yolo_image)
    return frame_list


def image_output(video_name, save_count, curr_frame, orig_frame_with_lines_dilated, dilated, orig_frame_with_lines):
    # 保存原图像
    orig_name = video_name + f"/orig_{save_count}.jpg"
    cv2.imwrite(orig_name, curr_frame)
    # 输出阈值图像
    threshold_name = video_name + f"/threshold_1_{save_count}.jpg"
    cv2.imwrite(threshold_name, orig_frame_with_lines_dilated)
    threshold_name = video_name + f"/threshold_2_{save_count}.jpg"
    cv2.imwrite(threshold_name, dilated)

    # 输出原图with变化图上的lines
    # frame_name = video_name + f"/frame_{save_count}.jpg"
    # cv2.imwrite(frame_name, frame_with_lines)

    # # 输出边缘图
    # frame_with_edges = cv2.cvtColor(edges, cv2.COLOR_GRAY2BGR)
    # edge_name = video_name + f"/edge_{save_count}.jpg"
    # cv2.imwrite(edge_name, frame_with_edges)
    # 输出原图with变化图上的lines
    # frame_name = video_name + f"/frame_{save_count}.jpg"
    # cv2.imwrite(frame_name, orig_frame)
    # 输出原图with原图上的lines
    frame_name = video_name + f"/orig_frame_{save_count}.jpg"
    cv2.imwrite(frame_name, orig_frame_with_lines)


def draw_smooth_lines(lst):
    data = np.array(lst)

    # 生成平滑曲线
    x = np.arange(len(data))
    x_smooth = np.linspace(x.min(), x.max(), 300)
    y_smooth = make_interp_spline(x, data)(x_smooth)

    # 绘制平滑折线图
    plt.plot(x_smooth, y_smooth)
    plt.xlabel('X')
    plt.ylabel('Y')
    plt.title('smooth')
    plt.grid(True)
    plt.show()


def smooth_x_t(lst):
    # 将 x_t 图平滑一下，将其中的0或者非常大的数值处理掉
    zero_subarrays = find_zero_subarrays(lst)
    for subarrays in zero_subarrays:
        # 最开始和结束的全0子数组忽略
        if subarrays[0] == 0 or subarrays[1] == len(lst) - 1:
            continue
        # 过长的0需要将其忽视掉
        elif subarrays[1] - subarrays[0] >= 3:
            if subarrays[0] < 3:  # 如果是前面，则将前面全部置为0
                for i in range(subarrays[0]):
                    lst[i] = 0
            if len(lst) - subarrays[1] < 3:
                for i in range(subarrays[1], len(lst)):
                    lst[i] = 0
        else:
            split = split_integer_range(lst[subarrays[0] - 1], lst[subarrays[1] + 1], subarrays[1] - subarrays[0] + 2)
            for i, x in enumerate(split):
                lst[subarrays[0] + i] = x
    # print(lst)
    return lst


def smooth_start_end(lst):
    # 过滤掉开始和结束突然出现的很大的值
    zero_subarrays = find_zero_subarrays(lst)
    for subarrays in zero_subarrays:
        # 最开始和结束的全0子数组忽略
        if subarrays[0] == 0 or subarrays[1] == len(lst) - 1:
            continue
        # 过长的0需要将其忽视掉
        elif subarrays[1] - subarrays[0] >= 3:
            if subarrays[0] < 3:  # 如果是前面，则将前面全部置为0
                for i in range(subarrays[0]):
                    lst[i] = 0
            if len(lst) - subarrays[1] < 3:
                for i in range(subarrays[1], len(lst)):
                    lst[i] = 0
    print(lst)
    return lst


def find_zero_subarrays(lst):
    # 找到数组的0子数组的开始下标和结束下标
    zero_subarrays = []
    start = None

    for i, num in enumerate(lst):
        if num == 0:
            if start is None:
                start = i
        elif start is not None:
            zero_subarrays.append((start, i - 1))
            start = None

    if start is not None:
        zero_subarrays.append((start, len(lst) - 1))
    return zero_subarrays


def split_integer_range(a, b, n):
    if n <= 0:
        return []

    num_range = b - a + 1  # 整数范围
    chunk_size = num_range // n  # 每份的大小
    remainder = num_range % n  # 余数

    result = []
    current_num = a
    for i in range(n):
        chunk_end = current_num + chunk_size - 1
        if remainder > 0:
            chunk_end += 1
            remainder -= 1
        result.append(chunk_end)
        current_num = chunk_end + 1
    return result


def v_t_list_2_x_y(v_t_list):
    # 将 x-t 列表转化为 x 和 y 两个列表
    x = list()
    y = list()
    for i, v in enumerate(v_t_list):
        if v != 0:
            x.append(i)
            y.append(v)
    return x, y


def v_t_2_a_t(v_t):
    a_t = list()
    for i in range(1, len(v_t)):
        if v_t[i] == 0 and v_t[i - 1] == 0:
            a_t.append(None)
        else:
            a_t.append(v_t[i] - v_t[i - 1])
    x = list()
    y = list()
    for i, a in enumerate(a_t):
        if a is not None:
            x.append(i)
            y.append(a)
    return x, y


def fit_curve(x, y, n):
    """
    用于拟合曲线
    :param x: x坐标
    :param y: y坐标
    :param n: 曲线的次方
    :return: 曲线公式
    """
    coeff = np.polyfit(x, y, n)
    poly_fit = np.poly1d(coeff)
    # poly_fit.deriv()  求导
    print(poly_fit)
    return poly_fit, coeff


def get_mse(x, y, poly_fit, width):
    # 计算均方误差，若 mse >= (屏幕宽度/20)^2 则异常
    y_pre = poly_fit(x)
    is_smooth = True
    mse = mean_squared_error(y, y_pre)
    if mse >= (width / 20) ** 2:  # MSE超过  (屏幕宽度/20)*2 则为不平滑
        is_smooth = False
    print(mse, is_smooth)
    return mse, is_smooth


def draw_fit_curve(x, y, poly_fit):
    # 画拟合曲线
    plt.xlabel('t')
    plt.ylabel('v')
    plt.title('smooth')
    plt.grid(True)
    x_pre = [i for i in range(min(x), max(x) + 1)]
    y_pre = poly_fit(x_pre)
    plt.plot(x, y, '.')
    plt.plot(x_pre, y_pre)
    plt.show()


def get_abnormal_value(x, y, poly_fit):
    y_pre = poly_fit(x)
    is_smooth = True
    delta = (max(y) - min(y)) / 6
    print(f'delta : {delta}')
    abnormal_index = list()  # 异常数据的下标
    for i in range(len(y)):
        if abs(y[i] - y_pre[i]) >= delta:
            abnormal_index.append((i, y[i]))
    if len(abnormal_index) > 0.1 * (max(x) - min(x)):  # 异常数据占比超过 [max(t) – min(t)] / 10 则不平滑
        is_smooth = False
    print(abnormal_index, is_smooth)
    return abnormal_index, is_smooth


def analyze_fit(x, coeff):
    # 判断是否是单调升/降函数 or 是处于先上升后下降的阶段  --> 若是先下降后上升则，不合理
    a, b, c, d = coeff
    poly_fit = np.poly1d(coeff)
    delta = 4 * b ** 2 - 12 * a * c
    if (a > 0 and delta <= 0) or (a < 0 and delta <= 0):  # (a > 0 且 Δ <= 0) 或者 (a < 0 且 Δ >= 0) 则单调，符合条件
        return True
    if poly_fit(min(x)) < poly_fit(min(x) + 1):  # 非单调的情况，需要目前是上升阶段
        return True
    return False


def slide_is_smooth(x_t_list_without_repeat):
    # 判断转场是否平滑
    smooth_v_t = smooth_start_end(x_t_list_without_repeat)
    # v-t 图处理
    x_v, y_v = v_t_list_2_x_y(smooth_v_t)
    poly_fit, coeff = fit_curve(x_v, y_v, 2)
    # 分析
    mse, mse_pass = get_mse(x_v, y_v, poly_fit, width)
    abnormal_list, abnormal_pass = get_abnormal_value(x_v, y_v, poly_fit)
    fit_pass = analyze_fit(x_v, coeff)
    draw_fit_curve(x_v, y_v, poly_fit)
    if fit_pass and mse_pass and abnormal_pass:
        return True
    return False


def run(video):
    frame_list = get_video_change(video)
    x_t_list_without_repeat = get_frames_same_line_2(frame_list, Direction.Left)
    slide_is_smooth(x_t_list_without_repeat)


if __name__ == '__main__':
    # 打开视频文件
    """
    'slide1_1', Direction.Right
    'slide2_1', Direction.Left
    'slide3', Direction.Right
    'slide4', Direction.Left
    'slide5', Direction.Left
    'pc', Direction.Right
    'pc2', Direction.Left
    'pc3', Direction.Left
    """
    width = 1080
    video_name, direction = 'slide3', Direction.Right

    # get_x_t_graph_3(dict3, Direction.Left, width)
    # exit(1)
    """
    方法1 的数据
    list_slide_1_1 = [0, 0, 0, 89, 247, 150, 0, 0, 72, 43, 26, 10, 0, 4, 6]
    list_slide_2_1 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 149, 203, 256, 0, 131, 140, 63, 64, 0, 25, 27, 9, 7]
    list_slide_3 = [0, 879, 0, 0, 386, 0, 152, 102, 13]
    list_pc = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 68, 172, 258, 111, 93, 78, 119, 93, 39, 92, 47, 56, 0, 16, 0, 3, 5, 15, 12]
    list_pc2 = [0, 0, 0, 0, 0, 148, 150, 100, 199, 150, 101, 98, 100, 50, 0, 150, 51]
    list_pc3 = [0, 0, 277, 0, 80, 0, 250, 0, 99, 148, 0, 157, 0, 0, 146]
    """
    list_slide_1_1 = [0, 0, 0, 89, 251, 148, 0, 0, 0, 43, 26, 0, 10, 4, 318]  # 不平滑
    list_slide_2_1 = [537, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 148, 199, 0, 257, 127, 138, 61, 0, 61, 25, 26, 0, 0, 7, 2, 0, 0, 0]  # 不平滑
    list_slide_3 = [0, 0, 0, 0, 0, 386, 148, 102, 13, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 41]  # 平滑
    list_slide_4 = [0, 0, 0, 0, 0, 310, 187, 79, 74, 43, 18, 0, 0, 0, 0, 351]  # 平滑
    list_slide_5 = [1054, 0, 0, 255, 197, 134, 83, 50, 29, 11, 0, 0, 0]  # 平滑
    list_pc = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 69, 171, 258, 111, 93, 78, 120, 92, 39, 15, 0, 0, 59, 14, 0, 0, 2, 2, 0, 1, 0, 0, 42]  # # 平滑
    list_pc2 = [0, 0, 0, 0, 0, 148, 150, 100, 199, 150, 101, 98, 100, 0, 50, 150, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 44]  # 不平滑
    list_pc3 = [0, 0, 0, 0, 80, 151, 99, 0, 99, 148, 0, 250, 0, 0, 299, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

    # smooth_x_t = smooth_x_t([0, 0, 0, 3, 148, 199, 0, 257, 127, 138, 61, 0, 61, 25, 26, 0, 0, 7, 2, 0, 0, 0])
    # draw_smooth_lines(smooth_x_t)

    METHOD = 3
    if METHOD == 1:  # 用全部帧的方法
        frame_list = get_video_change(video_name)
        classified_lines_list = get_frames_same_line(frame_list, direction)
        print_same_line(video_name, frame_list, classified_lines_list)
        x_t_list_without_repeat = get_x_t_graph_3(classified_lines_list, direction, width)
        draw_smooth_lines(x_t_list_without_repeat)
    elif METHOD == 2:  # 用相邻帧的方法
        frame_list = get_video_change(video_name)
        x_t_list_without_repeat = get_frames_same_line_2(frame_list, direction)
        slide_is_smooth(x_t_list_without_repeat)
    elif METHOD == 3:  # 测试函数
        slide_is_smooth(list_slide_3)
        # a-t 图处理
        # x_a, y_a = v_t_2_a_t(smooth_v_t)
        # poly_fit = fit_curve(x_a, y_a, 1)
        # mse, _ = get_mse(x_a, y_a, poly_fit, width)
        # draw_fit_curve(x_a, y_a, poly_fit)
    # TODO: 目前两个问题
    # 111. 需要将第一个变化很大的数据给去除，就是从0到有的那个数据
    # 1. 只用找相邻两帧之间的相同线段，不用在所有帧之间找 ！！不能用一个线段找最相似的，要全部计算得分之后找到最相似的，一会画出来看一下(解决）
    # 2. 结合易新的数据
    # 3. 移动的方向判断（两个方向都尝试）
    # yolo: 找到动的，静的不要；优先选非text， UpperTaskBar的；动的标准要定一下，有可能中心点发生变化但是左右边界有一边没有变；优先找相似度高的
